/********************************************************************
	Copyright (C) 2006-2008 Lensow
	http://www.lensow.com
	Created:	2008/06/07   10:46
	Filename: 	x:\Lensow\Loplayer\src\libcore\FGFilter.cpp
	Author:		Dean

	Purpose:
*********************************************************************/

#include "stdafx.h"
#include "FGFilter.h"
#include "..\DSUtil\DSUtil.h"
#include "DX7AllocatorPresenter.h"
#include "DX9AllocatorPresenter.h"
#include "EVRAllocatorPresenter.h"
#include <moreuuids.h>


//
// CFGFilter
//

CFGFilter::CFGFilter(const CLSID& clsid, LPCWSTR name, UINT64 merit)
		: m_clsid(clsid)
		, m_name(name)
{
	m_merit.val = merit;
}

const CAtlList<GUID>& CFGFilter::GetTypes() const
{
	return m_types;
}

void CFGFilter::SetTypes(const CAtlList<GUID>& types)
{
	m_types.RemoveAll();
	m_types.AddTailList(&types);
}

void CFGFilter::AddType(const GUID& majortype, const GUID& subtype)
{
	m_types.AddTail(majortype);
	m_types.AddTail(subtype);
}

bool CFGFilter::CheckTypes(const CAtlArray<GUID>& types, bool fExactMatch)
{
	POSITION pos = m_types.GetHeadPosition();
	while (pos) {
		const GUID& majortype = m_types.GetNext(pos);
		if (!pos) {
			ASSERT(0);
			break;
		}
		const GUID& subtype = m_types.GetNext(pos);

		for (int i = 0, len = types.GetCount() & ~1; i < len; i += 2) {
			if (fExactMatch) {
				if (majortype == types[i] && majortype != GUID_NULL
						&& subtype == types[i+1] && subtype != GUID_NULL)
					return true;
			} else {
				if ((majortype == GUID_NULL || types[i] == GUID_NULL || majortype == types[i])
						&& (subtype == GUID_NULL || types[i+1] == GUID_NULL || subtype == types[i+1]))
					return true;
			}
		}
	}

	return false;
}

//
// CFGFilterRegistry
//

CFGFilterRegistry::CFGFilterRegistry(IMoniker* pMoniker, UINT64 merit)
		: CFGFilter(GUID_NULL, L"", merit)
		, m_pMoniker(pMoniker)
{
	if (!m_pMoniker) return;

	LPOLESTR str = NULL;
	if (FAILED(m_pMoniker->GetDisplayName(0, 0, &str))) return;
	m_DisplayName = m_name = str;
	CoTaskMemFree(str), str = NULL;

	CComPtr<IPropertyBag> pPB;
	if (SUCCEEDED(m_pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pPB))) {
		CComVariant var;
		if (SUCCEEDED(pPB->Read(CComBSTR(_T("FriendlyName")), &var, NULL))) {
			m_name = var.bstrVal;
			var.Clear();
		}

		if (SUCCEEDED(pPB->Read(CComBSTR(_T("CLSID")), &var, NULL))) {
			CLSIDFromString(var.bstrVal, &m_clsid);
			var.Clear();
		}

		if (SUCCEEDED(pPB->Read(CComBSTR(_T("FilterData")), &var, NULL))) {
			BSTR* pstr;
			if (SUCCEEDED(SafeArrayAccessData(var.parray, (void**)&pstr))) {
				ExtractFilterData((BYTE*)pstr, var.parray->cbElements*(var.parray->rgsabound[0].cElements));
				SafeArrayUnaccessData(var.parray);
			}

			var.Clear();
		}
	}

	if (merit != MERIT64_DO_USE) m_merit.val = merit;
}

CFGFilterRegistry::CFGFilterRegistry(LPCWSTR DisplayName, UINT64 merit)
		: CFGFilter(GUID_NULL, L"", merit)
		, m_DisplayName(DisplayName)
{
	if (m_DisplayName.IsEmpty()) return;

	CComPtr<IBindCtx> pBC;
	CreateBindCtx(0, &pBC);

	ULONG chEaten;
	if (S_OK != MkParseDisplayName(pBC, CComBSTR(m_DisplayName), &chEaten, &m_pMoniker))
		return;

	CComPtr<IPropertyBag> pPB;
	if (SUCCEEDED(m_pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pPB))) {
		CComVariant var;
		if (SUCCEEDED(pPB->Read(CComBSTR(_T("FriendlyName")), &var, NULL))) {
			m_name = var.bstrVal;
			var.Clear();
		}

		if (SUCCEEDED(pPB->Read(CComBSTR(_T("CLSID")), &var, NULL))) {
			CLSIDFromString(var.bstrVal, &m_clsid);
			var.Clear();
		}

		if (SUCCEEDED(pPB->Read(CComBSTR(_T("FilterData")), &var, NULL))) {
			BSTR* pstr;
			if (SUCCEEDED(SafeArrayAccessData(var.parray, (void**)&pstr))) {
				ExtractFilterData((BYTE*)pstr, var.parray->cbElements*(var.parray->rgsabound[0].cElements));
				SafeArrayUnaccessData(var.parray);
			}

			var.Clear();
		}
	}

	if (merit != MERIT64_DO_USE) m_merit.val = merit;
}

CFGFilterRegistry::CFGFilterRegistry(const CLSID& clsid, UINT64 merit)
		: CFGFilter(clsid, L"", merit)
{
	if (m_clsid == GUID_NULL) return;

	CString guid = CStringFromGUID(m_clsid);

	CRegKey key;

	if (ERROR_SUCCESS == key.Open(HKEY_CLASSES_ROOT, _T("CLSID\\") + guid, KEY_READ)) {
		ULONG nChars = 0;
		if (ERROR_SUCCESS == key.QueryStringValue(NULL, NULL, &nChars)) {
			CString name;
			if (ERROR_SUCCESS == key.QueryStringValue(NULL, name.GetBuffer(nChars), &nChars)) {
				name.ReleaseBuffer(nChars);
				m_name = name;
			}
		}

		key.Close();
	}

	CRegKey catkey;

	if (ERROR_SUCCESS == catkey.Open(HKEY_CLASSES_ROOT, _T("CLSID\\{083863F1-70DE-11d0-BD40-00A0C911CE86}\\Instance"), KEY_READ)) {
		if (ERROR_SUCCESS != key.Open(catkey, guid, KEY_READ)) {
			// illiminable pack uses the name of the filter and not the clsid, have to enum all keys to find it...

			FILETIME ft;
			TCHAR buff[256];
			DWORD len = countof(buff);
			for (DWORD i = 0; ERROR_SUCCESS == catkey.EnumKey(i, buff, &len, &ft); i++, len = countof(buff)) {
				if (ERROR_SUCCESS == key.Open(catkey, buff, KEY_READ)) {
					TCHAR clsid[256];
					len = countof(clsid);
					if (ERROR_SUCCESS == key.QueryStringValue(_T("CLSID"), clsid, &len) && GUIDFromCString(clsid) == m_clsid)
						break;

					key.Close();
				}
			}
		}

		if (key) {
			ULONG nChars = 0;
			if (ERROR_SUCCESS == key.QueryStringValue(_T("FriendlyName"), NULL, &nChars)) {
				CString name;
				if (ERROR_SUCCESS == key.QueryStringValue(_T("FriendlyName"), name.GetBuffer(nChars), &nChars)) {
					name.ReleaseBuffer(nChars);
					m_name = name;
				}
			}

			ULONG nBytes = 0;
			if (ERROR_SUCCESS == key.QueryBinaryValue(_T("FilterData"), NULL, &nBytes)) {
				CAutoVectorPtr<BYTE> buff;
				if (buff.Allocate(nBytes) && ERROR_SUCCESS == key.QueryBinaryValue(_T("FilterData"), buff, &nBytes)) {
					ExtractFilterData(buff, nBytes);
				}
			}

			key.Close();
		}
	}

	if (merit != MERIT64_DO_USE) m_merit.val = merit;
}

HRESULT CFGFilterRegistry::Create(IBaseFilter** ppBF, CInterfaceList<IUnknown, &IID_IUnknown>& pUnks)
{
	CheckPointer(ppBF, E_POINTER);

	HRESULT hr = E_FAIL;

	if (m_pMoniker) {
		if (SUCCEEDED(hr = m_pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)ppBF))) {
			m_clsid = ::GetCLSID(*ppBF);
		}
	} else if (m_clsid != GUID_NULL) {
		CComQIPtr<IBaseFilter> pBF;

		if (FAILED(pBF.CoCreateInstance(m_clsid)))
			return E_FAIL;

		*ppBF = pBF.Detach();

		hr = S_OK;
	}

	return hr;
};

[uuid("97f7c4d4-547b-4a5f-8332-536430ad2e4d")]
interface IAMFilterData : public IUnknown {
	STDMETHOD (ParseFilterData) (BYTE* rgbFilterData, ULONG cb, BYTE** prgbRegFilter2) PURE;
	STDMETHOD (CreateFilterData) (REGFILTER2* prf2, BYTE** prgbFilterData, ULONG* pcb) PURE;
};

void CFGFilterRegistry::ExtractFilterData(BYTE* p, UINT len)
{
	CComPtr<IAMFilterData> pFD;
	BYTE* ptr = NULL;

	if (SUCCEEDED(pFD.CoCreateInstance(CLSID_FilterMapper2))
			&& SUCCEEDED(pFD->ParseFilterData(p, len, (BYTE**)&ptr))) {
		REGFILTER2* prf = (REGFILTER2*) * (DWORD*)ptr; // this is f*cked up

		m_merit.mid = prf->dwMerit;

		if (prf->dwVersion == 1) {
			for (UINT i = 0; i < prf->cPins; i++) {
				if (prf->rgPins[i].bOutput)
					continue;

				for (UINT j = 0; j < prf->rgPins[i].nMediaTypes; j++) {
					if (!prf->rgPins[i].lpMediaType[j].clsMajorType || !prf->rgPins[i].lpMediaType[j].clsMinorType)
						break;

					const REGPINTYPES& rpt = prf->rgPins[i].lpMediaType[j];
					AddType(*rpt.clsMajorType, *rpt.clsMinorType);
				}
			}
		} else if (prf->dwVersion == 2) {
			for (UINT i = 0; i < prf->cPins2; i++) {
				if (prf->rgPins2[i].dwFlags&REG_PINFLAG_B_OUTPUT)
					continue;

				for (UINT j = 0; j < prf->rgPins2[i].nMediaTypes; j++) {
					if (!prf->rgPins2[i].lpMediaType[j].clsMajorType || !prf->rgPins2[i].lpMediaType[j].clsMinorType)
						break;

					const REGPINTYPES& rpt = prf->rgPins2[i].lpMediaType[j];
					AddType(*rpt.clsMajorType, *rpt.clsMinorType);
				}
			}
		}

		CoTaskMemFree(prf);
	} else {
		BYTE* base = p;

#define ChkLen(size) if(p - base + size > (int)len) return;

		ChkLen(4)
		if (*(DWORD*)p != 0x00000002) return; // only version 2 supported, no samples found for 1
		p += 4;

		ChkLen(4)
		m_merit.mid = *(DWORD*)p;
		p += 4;

		m_types.RemoveAll();

		ChkLen(8)
		DWORD nPins = *(DWORD*)p;
		p += 8;
		while (nPins-- > 0) {
			ChkLen(1)
			BYTE n = *p - 0x30;
			p++;

			ChkLen(2)
			WORD pi = *(WORD*)p;
			p += 2;
			ASSERT(pi == 'ip');

			ChkLen(1)
			BYTE x33 = *p;
			p++;
			ASSERT(x33 == 0x33);

			ChkLen(8)
			bool fOutput = !!(*p & REG_PINFLAG_B_OUTPUT);
			p += 8;

			ChkLen(12)
			DWORD nTypes = *(DWORD*)p;
			p += 12;
			while (nTypes-- > 0) {
				ChkLen(1)
				BYTE n = *p - 0x30;
				p++;

				ChkLen(2)
				WORD ty = *(WORD*)p;
				p += 2;
				ASSERT(ty == 'yt');

				ChkLen(5)
				BYTE x33 = *p;
				p++;
				ASSERT(x33 == 0x33);
				p += 4;

				ChkLen(8)
				if (*(DWORD*)p < (p - base + 8) || *(DWORD*)p >= len
						|| *(DWORD*)(p + 4) < (p - base + 8) || *(DWORD*)(p + 4) >= len) {
					p += 8;
					continue;
				}

				GUID majortype, subtype;
				memcpy(&majortype, &base[*(DWORD*)p], sizeof(GUID));
				p += 4;
				if (!fOutput) AddType(majortype, subtype);
			}
		}

#undef ChkLen
	}
}

//
// CFGFilterFile
//

CFGFilterFile::CFGFilterFile(const CLSID& clsid, LPCTSTR path, LPCWSTR name, UINT64 merit)
		: CFGFilter(clsid, name, merit)
		, m_path(path)
		, m_hInst(NULL)
{
}

HRESULT CFGFilterFile::Create(IBaseFilter** ppBF, CInterfaceList<IUnknown, &IID_IUnknown>& pUnks)
{
	CheckPointer(ppBF, E_POINTER);

	return LoadExternalFilter(m_path, m_clsid, ppBF);
}

//
// CFGFilterVideoRenderer
//

CFGFilterVideoRenderer::CFGFilterVideoRenderer(HWND hWnd, const CLSID& clsid, LPCWSTR name, UINT64 merit)
		: CFGFilter(clsid, name, merit)
		, m_hWnd(hWnd)
{
	AddType(MEDIATYPE_Video, MEDIASUBTYPE_NULL);
}

HRESULT CFGFilterVideoRenderer::Create(IBaseFilter** ppBF, CInterfaceList<IUnknown, &IID_IUnknown>& pUnks)
{
	CheckPointer(ppBF, E_POINTER);

	HRESULT hr = S_OK;

	CComPtr<ISubPicAllocatorPresenter> pCAP;

	if (m_clsid == CLSID_VMR7AllocatorPresenter
			|| m_clsid == CLSID_VMR9AllocatorPresenter
			|| m_clsid == CLSID_DXRAllocatorPresenter
			|| m_clsid == CLSID_EVRAllocatorPresenter) {
		if (SUCCEEDED(CreateAP7(m_clsid, m_hWnd, &pCAP))
				|| SUCCEEDED(CreateAP9(m_clsid, m_hWnd, &pCAP))
				|| SUCCEEDED(CreateEVR(m_clsid, m_hWnd, &pCAP))) {
			CComPtr<IUnknown> pRenderer;
			if (SUCCEEDED(hr = pCAP->CreateRenderer(&pRenderer))) {
				*ppBF = CComQIPtr<IBaseFilter>(pRenderer).Detach();
				pUnks.AddTail(pCAP);
			}
		}
	} else {
		CComPtr<IBaseFilter> pBF;
		if (SUCCEEDED(pBF.CoCreateInstance(m_clsid))) {
			BeginEnumPins(pBF, pEP, pPin) {
				if (CComQIPtr<IMixerPinConfig, &IID_IMixerPinConfig> pMPC = pPin) {
					pUnks.AddTail(pMPC);
					break;
				}
			}
			EndEnumPins

			*ppBF = pBF.Detach();
		}
	}

	if (!*ppBF) hr = E_FAIL;

	return hr;
}

//
// CFGFilterList
//

CFGFilterList::CFGFilterList()
{
}

CFGFilterList::~CFGFilterList()
{
	RemoveAll();
}

void CFGFilterList::RemoveAll()
{
	while (!m_filters.IsEmpty()) {
		const filter_t& f = m_filters.RemoveHead();
		if (f.autodelete) delete f.pFGF;
	}

	m_sortedfilters.RemoveAll();
}

void CFGFilterList::Insert(CFGFilter* pFGF, int group, bool exactmatch, bool autodelete)
{
	if (CFGFilterRegistry* f1r = dynamic_cast<CFGFilterRegistry*>(pFGF)) {
		POSITION pos = m_filters.GetHeadPosition();
		while (pos) {
			filter_t& f2 = m_filters.GetNext(pos);

			if (group != f2.group) continue;

			if (CFGFilterRegistry* f2r = dynamic_cast<CFGFilterRegistry*>(f2.pFGF)) {
				if (f1r->GetMoniker() && f2r->GetMoniker() && S_OK == f1r->GetMoniker()->IsEqual(f2r->GetMoniker())
						|| f1r->GetCLSID() != GUID_NULL && f1r->GetCLSID() == f2r->GetCLSID()) {
					TRACE(_T("FGM: Inserting %d %d %016I64x '%s' NOT!\n"),
						  group, exactmatch, pFGF->GetMerit(),
						  pFGF->GetName().Length() ? CString(pFGF->GetName()):CStringFromGUID(pFGF->GetCLSID()));

					if (autodelete) delete pFGF;
					return;
				}
			}
		}
	}

	POSITION pos = m_filters.GetHeadPosition();
	while (pos) {
		if (m_filters.GetNext(pos).pFGF == pFGF) {
			TRACE(_T("FGM: Inserting %d %d %016I64x '%s' DUP!\n"),
				  group, exactmatch, pFGF->GetMerit(),
				  pFGF->GetName().Length() ? CString(pFGF->GetName()):CStringFromGUID(pFGF->GetCLSID()));

			if (autodelete) delete pFGF;
			return;
		}
	}

	TRACE(_T("FGM: Inserting %d %d %016I64x '%s'\n"),
		  group, exactmatch, pFGF->GetMerit(),
		  pFGF->GetName().Length() ? CString(pFGF->GetName()):CStringFromGUID(pFGF->GetCLSID()));

	filter_t f = {m_filters.GetCount(), pFGF, group, exactmatch, autodelete};
	m_filters.AddTail(f);

	m_sortedfilters.RemoveAll();
}

POSITION CFGFilterList::GetHeadPosition()
{
	if (m_sortedfilters.IsEmpty()) {
		CAtlArray<filter_t> sort;
		sort.SetCount(m_filters.GetCount());
		POSITION pos = m_filters.GetHeadPosition();
		for (int i = 0; pos; i++) sort[i] = m_filters.GetNext(pos);
		qsort(&sort[0], sort.GetCount(), sizeof(sort[0]), filter_cmp);
		for (size_t i = 0; i < sort.GetCount(); i++)
			if (sort[i].pFGF->GetMerit() >= MERIT64_DO_USE)
				m_sortedfilters.AddTail(sort[i].pFGF);
	}

	TRACE(_T("FGM: Sorting filters\n"));

	POSITION pos = m_sortedfilters.GetHeadPosition();
	while (pos) {
		CFGFilter* pFGF = m_sortedfilters.GetNext(pos);
		TRACE(_T("FGM: - %016I64x '%s'\n"), pFGF->GetMerit(), 
		pFGF->GetName().Length() ? CString(pFGF->GetName()):CStringFromGUID(pFGF->GetCLSID()));
	}

	return m_sortedfilters.GetHeadPosition();
}

CFGFilter* CFGFilterList::GetNext(POSITION& pos) const
{
	return m_sortedfilters.GetNext(pos);
}

int CFGFilterList::filter_cmp(const void* a, const void* b)
{
	filter_t* fa = (filter_t*)a;
	filter_t* fb = (filter_t*)b;

	if (fa->group < fb->group) return -1;
	if (fa->group > fb->group) return + 1;

	if (fa->pFGF->GetCLSID() == fb->pFGF->GetCLSID()) {
		CFGFilterFile* fgfa = dynamic_cast<CFGFilterFile*>(fa->pFGF);
		CFGFilterFile* fgfb = dynamic_cast<CFGFilterFile*>(fb->pFGF);

		if (fgfa && !fgfb) return -1;
		if (!fgfa && fgfb) return + 1;
	}

	if (fa->pFGF->GetMerit() > fb->pFGF->GetMerit()) return -1;
	if (fa->pFGF->GetMerit() < fb->pFGF->GetMerit()) return + 1;

	if (fa->exactmatch && !fb->exactmatch) return -1;
	if (!fa->exactmatch && fb->exactmatch) return + 1;

	if (fa->index < fb->index) return -1;
	if (fa->index > fb->index) return + 1;

	return 0;
}
